/*
 * Copyright (c) 2007 innoSysTec (R) GmbH, Germany. All rights reserved.
 * Original author: Edmund Wagner
 * Creation date: 31.05.2007
 *
 * Source: $HeadURL$
 * Last changed: $LastChangedDate$
 * 
 * the unrar licence applies to all junrar source and binary distributions 
 * you are not allowed to use this source to re-create the RAR compression algorithm
 * 
 * Here some html entities which can be used for escaping javadoc tags:
 * "&":  "&#038;" or "&amp;"
 * "<":  "&#060;" or "&lt;"
 * ">":  "&#062;" or "&gt;"
 * "@":  "&#064;" 
 */
package com.github.junrar.unpack;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import com.github.junrar.exception.RarException;
import com.github.junrar.unpack.decode.Compress;
import com.github.junrar.unpack.ppm.BlockTypes;
import com.github.junrar.unpack.ppm.ModelPPM;
import com.github.junrar.unpack.ppm.SubAllocator;
import com.github.junrar.unpack.vm.BitInput;
import com.github.junrar.unpack.vm.RarVM;
import com.github.junrar.unpack.vm.VMPreparedProgram;

/**
 * DOCUMENT ME
 * 
 * @author $LastChangedBy$
 * @version $LastChangedRevision$
 */
public final class Unpack extends Unpack20
 {

  private final ModelPPM ppm = new ModelPPM();

  private int ppmEscChar;

  private RarVM rarVM = new RarVM();

  /* Filters code, one entry per filter */
  private List<UnpackFilter> filters = new ArrayList<UnpackFilter>();

  /* Filters stack, several entrances of same filter are possible */
  private List<UnpackFilter> prgStack = new ArrayList<UnpackFilter>();

  /*
   * lengths of preceding blocks, one length per filter. Used to reduce size
   * required to write block length if lengths are repeating
   */
  private List<Integer> oldFilterLengths = new ArrayList<Integer>();

  private int lastFilter;

  private boolean tablesRead;

  private byte[] unpOldTable = new byte[Compress.HUFF_TABLE_SIZE];

  private BlockTypes unpBlockType;

  private boolean externalWindow;

  private long writtenFileSize;

  private boolean fileExtracted;

  private boolean ppmError;

  private int prevLowDist;

  private int lowDistRepCount;

  public static int[] DBitLengthCounts = {4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12};

  public Unpack(ComprDataIO DataIO)
   {
    unpIO = DataIO;
    window = null;
    externalWindow = false;
    suspended = false;
    unpAllBuf = false;
    unpSomeRead = false;
   }

  public void init(byte[] window)
   {
    if (window == null)
     {
      this.window = new byte[Compress.MAXWINSIZE];
     }
    else
     {
      this.window = window;
      externalWindow = true;
     }
    inAddr = 0;
    unpInitData(false);
   }

  public void doUnpack(int method, boolean solid) throws IOException, RarException
   {
    if (unpIO.getSubHeader().getUnpMethod() == 0x30)
     {
      unstoreFile();
     }
    switch (method)
     {
     case 15: // rar 1.5 compression
      unpack15(solid);
      break;
     case 20: // rar 2.x compression
     case 26: // files larger than 2GB
      unpack20(solid);
      break;
     case 29: // rar 3.x compression
     case 36: // alternative hash
      unpack29(solid);
      break;
     }
   }

  private void unstoreFile() throws IOException, RarException
   {
    byte[] buffer = new byte[0x10000];
    while (true)
     {
      int code = unpIO.unpRead(buffer, 0, (int) Math.min(buffer.length, destUnpSize));
      if (code == 0 || code == -1)
       break;
      code = code < destUnpSize ? code : (int) destUnpSize;
      unpIO.unpWrite(buffer, 0, code);
      if (destUnpSize >= 0)
       destUnpSize -= code;
     }

   }

  private void unpack29(boolean solid) throws IOException, RarException
   {

    int[] DDecode = new int[Compress.DC];
    byte[] DBits = new byte[Compress.DC];

    int Bits;

    if (DDecode[1] == 0)
     {
      int Dist = 0, BitLength = 0, Slot = 0;
      for (int I = 0; I < DBitLengthCounts.length; I++, BitLength++)
       {
        int count = DBitLengthCounts[I];
        for (int J = 0; J < count; J++, Slot++, Dist += (1 << BitLength))
         {
          DDecode[Slot] = Dist;
          DBits[Slot] = (byte) BitLength;
         }
       }
     }

    fileExtracted = true;

    if (!suspended)
     {
      unpInitData(solid);
      if (!unpReadBuf())
       {
        return;
       }
      if ((!solid || !tablesRead) && !readTables())
       {
        return;
       }
     }

    if (ppmError)
     {
      return;
     }

    while (true)
     {
      unpPtr &= Compress.MAXWINMASK;

      if (inAddr > readBorder)
       {
        if (!unpReadBuf())
         {
          break;
         }
       }
      // System.out.println(((wrPtr - unpPtr) &
      // Compress.MAXWINMASK)+":"+wrPtr+":"+unpPtr);
      if (((wrPtr - unpPtr) & Compress.MAXWINMASK) < 260 && wrPtr != unpPtr)
       {

        UnpWriteBuf();
        if (writtenFileSize > destUnpSize)
         {
          return;
         }
        if (suspended)
         {
          fileExtracted = false;
          return;
         }
       }
      if (unpBlockType == BlockTypes.BLOCK_PPM)
       {
        int Ch = ppm.decodeChar();
        if (Ch == -1)
         {
          ppmError = true;
          break;
         }
        if (Ch == ppmEscChar)
         {
          int NextCh = ppm.decodeChar();
          if (NextCh == 0)
           {
            if (!readTables())
             {
              break;
             }
            continue;
           }
          if (NextCh == 2 || NextCh == -1)
           {
            break;
           }
          if (NextCh == 3)
           {
            if (!readVMCodePPM())
             {
              break;
             }
            continue;
           }
          if (NextCh == 4)
           {
            int Distance = 0, Length = 0;
            boolean failed = false;
            for (int I = 0; I < 4 && !failed; I++)
             {
              int ch = ppm.decodeChar();
              if (ch == -1)
               {
                failed = true;
               }
              else
               {
                if (I == 3)
                 {
                  // Bug fixed
                  Length = ch & 0xff;
                 }
                else
                 {
                  // Bug fixed
                  Distance = (Distance << 8) + (ch & 0xff);
                 }
               }
             }
            if (failed)
             {
              break;
             }
            copyString(Length + 32, Distance + 2);
            continue;
           }
          if (NextCh == 5)
           {
            int Length = ppm.decodeChar();
            if (Length == -1)
             {
              break;
             }
            copyString(Length + 4, 1);
            continue;
           }
         }
        window[unpPtr++] = (byte) Ch;
        continue;
       }

      int Number = decodeNumber(LD);
      if (Number < 256)
       {
        window[unpPtr++] = (byte) Number;
        continue;
       }
      if (Number >= 271)
       {
        int Length = LDecode[Number -= 271] + 3;
        if ((Bits = LBits[Number]) > 0)
         {
          Length += getbits() >>> (16 - Bits);
          addbits(Bits);
         }

        int DistNumber = decodeNumber(DD);
        int Distance = DDecode[DistNumber] + 1;
        if ((Bits = DBits[DistNumber]) > 0)
         {
          if (DistNumber > 9)
           {
            if (Bits > 4)
             {
              Distance += ((getbits() >>> (20 - Bits)) << 4);
              addbits(Bits - 4);
             }
            if (lowDistRepCount > 0)
             {
              lowDistRepCount--;
              Distance += prevLowDist;
             }
            else
             {
              int LowDist = decodeNumber(LDD);
              if (LowDist == 16)
               {
                lowDistRepCount = Compress.LOW_DIST_REP_COUNT - 1;
                Distance += prevLowDist;
               }
              else
               {
                Distance += LowDist;
                prevLowDist = LowDist;
               }
             }
           }
          else
           {
            Distance += getbits() >>> (16 - Bits);
            addbits(Bits);
           }
         }

        if (Distance >= 0x2000)
         {
          Length++;
          if (Distance >= 0x40000L)
           {
            Length++;
           }
         }

        insertOldDist(Distance);
        insertLastMatch(Length, Distance);

        copyString(Length, Distance);
        continue;
       }
      if (Number == 256)
       {
        if (!readEndOfBlock())
         {
          break;
         }
        continue;
       }
      if (Number == 257)
       {
        if (!readVMCode())
         {
          break;
         }
        continue;
       }
      if (Number == 258)
       {
        if (lastLength != 0)
         {
          copyString(lastLength, lastDist);
         }
        continue;
       }
      if (Number < 263)
       {
        int DistNum = Number - 259;
        int Distance = oldDist[DistNum];
        for (int I = DistNum; I > 0; I--)
         {
          oldDist[I] = oldDist[I - 1];
         }
        oldDist[0] = Distance;

        int LengthNumber = decodeNumber(RD);
        int Length = LDecode[LengthNumber] + 2;
        if ((Bits = LBits[LengthNumber]) > 0)
         {
          Length += getbits() >>> (16 - Bits);
          addbits(Bits);
         }
        insertLastMatch(Length, Distance);
        copyString(Length, Distance);
        continue;
       }
      if (Number < 272)
       {
        int Distance = SDDecode[Number -= 263] + 1;
        if ((Bits = SDBits[Number]) > 0)
         {
          Distance += getbits() >>> (16 - Bits);
          addbits(Bits);
         }
        insertOldDist(Distance);
        insertLastMatch(2, Distance);
        copyString(2, Distance);
        continue;
       }
     }
    UnpWriteBuf();

   }

  private void UnpWriteBuf() throws IOException
   {
    int WrittenBorder = wrPtr;
    int WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK;
    for (int I = 0; I < prgStack.size(); I++)
     {
      UnpackFilter flt = prgStack.get(I);
      if (flt == null)
       {
        continue;
       }
      if (flt.isNextWindow())
       {
        flt.setNextWindow(false);// ->NextWindow=false;
        continue;
       }
      int BlockStart = flt.getBlockStart();// ->BlockStart;
      int BlockLength = flt.getBlockLength();// ->BlockLength;
      if (((BlockStart - WrittenBorder) & Compress.MAXWINMASK) < WriteSize)
       {
        if (WrittenBorder != BlockStart)
         {
          UnpWriteArea(WrittenBorder, BlockStart);
          WrittenBorder = BlockStart;
          WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK;
         }
        if (BlockLength <= WriteSize)
         {
          int BlockEnd = (BlockStart + BlockLength) & Compress.MAXWINMASK;
          if (BlockStart < BlockEnd || BlockEnd == 0)
           {
            // VM.SetMemory(0,Window+BlockStart,BlockLength);
            rarVM.setMemory(0, window, BlockStart, BlockLength);
           }
          else
           {
            int FirstPartLength = Compress.MAXWINSIZE - BlockStart;
            // VM.SetMemory(0,Window+BlockStart,FirstPartLength);
            rarVM.setMemory(0, window, BlockStart, FirstPartLength);
            // VM.SetMemory(FirstPartLength,Window,BlockEnd);
            rarVM.setMemory(FirstPartLength, window, 0, BlockEnd);

           }

          VMPreparedProgram ParentPrg = filters.get(flt.getParentFilter()).getPrg();
          VMPreparedProgram Prg = flt.getPrg();

          if (ParentPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
           {
            // copy global data from previous script execution if
            // any
            // Prg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
            // memcpy(&Prg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
            Prg.getGlobalData().setSize(ParentPrg.getGlobalData().size());
            for (int i = 0; i < ParentPrg.getGlobalData().size() - RarVM.VM_FIXEDGLOBALSIZE; i++)
             {
              Prg.getGlobalData().set(RarVM.VM_FIXEDGLOBALSIZE + i,
                                      ParentPrg.getGlobalData().get(RarVM.VM_FIXEDGLOBALSIZE + i));
             }
           }

          ExecuteCode(Prg);

          if (Prg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
           {
            // save global data for next script execution
            if (ParentPrg.getGlobalData().size() < Prg.getGlobalData().size())
             {
              ParentPrg.getGlobalData().setSize(Prg.getGlobalData().size());// ->GlobalData.Alloc(Prg->GlobalData.Size());
             }
            // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&Prg->GlobalData[VM_FIXEDGLOBALSIZE],Prg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
            for (int i = 0; i < Prg.getGlobalData().size() - RarVM.VM_FIXEDGLOBALSIZE; i++)
             {
              ParentPrg.getGlobalData().set(RarVM.VM_FIXEDGLOBALSIZE + i,
                                            Prg.getGlobalData().get(RarVM.VM_FIXEDGLOBALSIZE + i));
             }
           }
          else
           {
            ParentPrg.getGlobalData().clear();
           }

          int FilteredDataOffset = Prg.getFilteredDataOffset();
          int FilteredDataSize = Prg.getFilteredDataSize();
          byte[] FilteredData = new byte[FilteredDataSize];

          for (int i = 0; i < FilteredDataSize; i++)
           {
            FilteredData[i] = rarVM.getMem()[FilteredDataOffset + i];// Prg.getGlobalData().get(FilteredDataOffset
            // +
            // i);
           }

          prgStack.set(I, null);
          while (I + 1 < prgStack.size())
           {
            UnpackFilter NextFilter = prgStack.get(I + 1);
            if (NextFilter == null || NextFilter.getBlockStart() != BlockStart
                || NextFilter.getBlockLength() != FilteredDataSize || NextFilter.isNextWindow())
             {
              break;
             }
            // apply several filters to same data block

            rarVM.setMemory(0, FilteredData, 0, FilteredDataSize);// .SetMemory(0,FilteredData,FilteredDataSize);

            VMPreparedProgram pPrg = filters.get(NextFilter.getParentFilter()).getPrg();
            VMPreparedProgram NextPrg = NextFilter.getPrg();

            if (pPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
             {
              // copy global data from previous script execution
              // if any
              // NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size());
              NextPrg.getGlobalData().setSize(pPrg.getGlobalData().size());
              // memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
              for (int i = 0; i < pPrg.getGlobalData().size() - RarVM.VM_FIXEDGLOBALSIZE; i++)
               {
                NextPrg.getGlobalData().set(RarVM.VM_FIXEDGLOBALSIZE + i,
                                            pPrg.getGlobalData().get(RarVM.VM_FIXEDGLOBALSIZE + i));
               }
             }

            ExecuteCode(NextPrg);

            if (NextPrg.getGlobalData().size() > RarVM.VM_FIXEDGLOBALSIZE)
             {
              // save global data for next script execution
              if (pPrg.getGlobalData().size() < NextPrg.getGlobalData().size())
               {
                pPrg.getGlobalData().setSize(NextPrg.getGlobalData().size());
               }
              // memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE);
              for (int i = 0; i < NextPrg.getGlobalData().size() - RarVM.VM_FIXEDGLOBALSIZE; i++)
               {
                pPrg.getGlobalData().set(RarVM.VM_FIXEDGLOBALSIZE + i,
                                         NextPrg.getGlobalData().get(RarVM.VM_FIXEDGLOBALSIZE + i));
               }
             }
            else
             {
              pPrg.getGlobalData().clear();
             }
            FilteredDataOffset = NextPrg.getFilteredDataOffset();
            FilteredDataSize = NextPrg.getFilteredDataSize();

            FilteredData = new byte[FilteredDataSize];
            for (int i = 0; i < FilteredDataSize; i++)
             {
              FilteredData[i] = NextPrg.getGlobalData().get(FilteredDataOffset + i);
             }

            I++;
            prgStack.set(I, null);
           }
          unpIO.unpWrite(FilteredData, 0, FilteredDataSize);
          unpSomeRead = true;
          writtenFileSize += FilteredDataSize;
          WrittenBorder = BlockEnd;
          WriteSize = (unpPtr - WrittenBorder) & Compress.MAXWINMASK;
         }
        else
         {
          for (int J = I; J < prgStack.size(); J++)
           {
            UnpackFilter filt = prgStack.get(J);
            if (filt != null && filt.isNextWindow())
             {
              filt.setNextWindow(false);
             }
           }
          wrPtr = WrittenBorder;
          return;
         }
       }
     }

    UnpWriteArea(WrittenBorder, unpPtr);
    wrPtr = unpPtr;

   }

  private void UnpWriteArea(int startPtr, int endPtr) throws IOException
   {
    if (endPtr != startPtr)
     {
      unpSomeRead = true;
     }
    if (endPtr < startPtr)
     {
      UnpWriteData(window, startPtr, -startPtr & Compress.MAXWINMASK);
      UnpWriteData(window, 0, endPtr);
      unpAllBuf = true;
     }
    else
     {
      UnpWriteData(window, startPtr, endPtr - startPtr);
     }
   }

  private void UnpWriteData(byte[] data, int offset, int size) throws IOException
   {
    if (writtenFileSize >= destUnpSize)
     {
      return;
     }
    int writeSize = size;
    long leftToWrite = destUnpSize - writtenFileSize;
    if (writeSize > leftToWrite)
     {
      writeSize = (int) leftToWrite;
     }
    unpIO.unpWrite(data, offset, writeSize);

    writtenFileSize += size;

   }

  private void insertOldDist(int distance)
   {
    oldDist[3] = oldDist[2];
    oldDist[2] = oldDist[1];
    oldDist[1] = oldDist[0];
    oldDist[0] = distance;
   }

  private void insertLastMatch(int length, int distance)
   {
    lastDist = distance;
    lastLength = length;
   }

  private void copyString(int length, int distance)
   {
    // System.out.println("copyString(" + length + ", " + distance + ")");

    int destPtr = unpPtr - distance;
    // System.out.println(unpPtr+":"+distance);
    if (destPtr >= 0 && destPtr < Compress.MAXWINSIZE - 260 && unpPtr < Compress.MAXWINSIZE - 260)
     {

      window[unpPtr++] = window[destPtr++];

      while (--length > 0)

       window[unpPtr++] = window[destPtr++];
     }
    else
     while (length-- != 0)
      {
       window[unpPtr] = window[destPtr++ & Compress.MAXWINMASK];
       unpPtr = (unpPtr + 1) & Compress.MAXWINMASK;
      }
   }

  protected void unpInitData(boolean solid)
   {
    if (!solid)
     {
      tablesRead = false;
      Arrays.fill(oldDist, 0); // memset(oldDist,0,sizeof(OldDist));

      oldDistPtr = 0;
      lastDist = 0;
      lastLength = 0;

      Arrays.fill(unpOldTable, (byte) 0);// memset(UnpOldTable,0,sizeof(UnpOldTable));

      unpPtr = 0;
      wrPtr = 0;
      ppmEscChar = 2;

      initFilters();
     }
    InitBitInput();
    ppmError = false;
    writtenFileSize = 0;
    readTop = 0;
    readBorder = 0;
    unpInitData20(solid);
   }

  private void initFilters()
   {
    oldFilterLengths.clear();
    lastFilter = 0;

    filters.clear();

    prgStack.clear();
   }

  private boolean readEndOfBlock() throws IOException, RarException
   {
    int BitField = getbits();
    boolean NewTable, NewFile = false;
    if ((BitField & 0x8000) != 0)
     {
      NewTable = true;
      addbits(1);
     }
    else
     {
      NewFile = true;
      NewTable = (BitField & 0x4000) != 0 ? true : false;
      addbits(2);
     }
    tablesRead = !NewTable;
    return !(NewFile || NewTable && !readTables());
   }

  private boolean readTables() throws IOException, RarException
   {
    byte[] bitLength = new byte[Compress.BC];

    byte[] table = new byte[Compress.HUFF_TABLE_SIZE];
    if (inAddr > readTop - 25)
     {
      if (!unpReadBuf())
       {
        return (false);
       }
     }
    faddbits((8 - inBit) & 7);
    long bitField = fgetbits() & 0xffFFffFF;
    if ((bitField & 0x8000) != 0)
     {
      unpBlockType = BlockTypes.BLOCK_PPM;
      return (ppm.decodeInit(this, ppmEscChar));
     }
    unpBlockType = BlockTypes.BLOCK_LZ;

    prevLowDist = 0;
    lowDistRepCount = 0;

    if ((bitField & 0x4000) == 0)
     {
      Arrays.fill(unpOldTable, (byte) 0);// memset(UnpOldTable,0,sizeof(UnpOldTable));
     }
    faddbits(2);

    for (int i = 0; i < Compress.BC; i++)
     {
      int length = (fgetbits() >>> 12) & 0xFF;
      faddbits(4);
      if (length == 15)
       {
        int zeroCount = (fgetbits() >>> 12) & 0xFF;
        faddbits(4);
        if (zeroCount == 0)
         {
          bitLength[i] = 15;
         }
        else
         {
          zeroCount += 2;
          while (zeroCount-- > 0 && i < bitLength.length)
           {
            bitLength[i++] = 0;
           }
          i--;
         }
       }
      else
       {
        bitLength[i] = (byte) length;
       }
     }

    makeDecodeTables(bitLength, 0, BD, Compress.BC);

    int TableSize = Compress.HUFF_TABLE_SIZE;

    for (int i = 0; i < TableSize;)
     {
      if (inAddr > readTop - 5)
       {
        if (!unpReadBuf())
         {
          return (false);
         }
       }
      int Number = decodeNumber(BD);
      if (Number < 16)
       {
        table[i] = (byte) ((Number + unpOldTable[i]) & 0xf);
        i++;
       }
      else if (Number < 18)
       {
        int N;
        if (Number == 16)
         {
          N = (fgetbits() >>> 13) + 3;
          faddbits(3);
         }
        else
         {
          N = (fgetbits() >>> 9) + 11;
          faddbits(7);
         }
        while (N-- > 0 && i < TableSize)
         {
          table[i] = table[i - 1];
          i++;
         }
       }
      else
       {
        int N;
        if (Number == 18)
         {
          N = (fgetbits() >>> 13) + 3;
          faddbits(3);
         }
        else
         {
          N = (fgetbits() >>> 9) + 11;
          faddbits(7);
         }
        while (N-- > 0 && i < TableSize)
         {
          table[i++] = 0;
         }
       }
     }
    tablesRead = true;
    if (inAddr > readTop)
     {
      return (false);
     }
    makeDecodeTables(table, 0, LD, Compress.NC);
    makeDecodeTables(table, Compress.NC, DD, Compress.DC);
    makeDecodeTables(table, Compress.NC + Compress.DC, LDD, Compress.LDC);
    makeDecodeTables(table, Compress.NC + Compress.DC + Compress.LDC, RD, Compress.RC);

    // memcpy(unpOldTable,table,sizeof(unpOldTable));
    for (int i = 0; i < unpOldTable.length; i++)
     {
      unpOldTable[i] = table[i];
     }
    return (true);

   }

  private boolean readVMCode() throws IOException, RarException
   {
    int FirstByte = getbits() >> 8;
    addbits(8);
    int Length = (FirstByte & 7) + 1;
    if (Length == 7)
     {
      Length = (getbits() >> 8) + 7;
      addbits(8);
     }
    else if (Length == 8)
     {
      Length = getbits();
      addbits(16);
     }
    List<Byte> vmCode = new ArrayList<Byte>();
    for (int I = 0; I < Length; I++)
     {
      if (inAddr >= readTop - 1 && !unpReadBuf() && I < Length - 1)
       {
        return (false);
       }
      vmCode.add(Byte.valueOf((byte) (getbits() >> 8)));
      addbits(8);
     }
    return (addVMCode(FirstByte, vmCode, Length));
   }

  private boolean readVMCodePPM() throws IOException, RarException
   {
    int FirstByte = ppm.decodeChar();
    if ((int) FirstByte == -1)
     {
      return (false);
     }
    int Length = (FirstByte & 7) + 1;
    if (Length == 7)
     {
      int B1 = ppm.decodeChar();
      if (B1 == -1)
       {
        return (false);
       }
      Length = B1 + 7;
     }
    else if (Length == 8)
     {
      int B1 = ppm.decodeChar();
      if (B1 == -1)
       {
        return (false);
       }
      int B2 = ppm.decodeChar();
      if (B2 == -1)
       {
        return (false);
       }
      Length = B1 * 256 + B2;
     }
    List<Byte> vmCode = new ArrayList<Byte>();
    for (int I = 0; I < Length; I++)
     {
      int Ch = ppm.decodeChar();
      if (Ch == -1)
       {
        return (false);
       }
      vmCode.add(Byte.valueOf((byte) Ch));// VMCode[I]=Ch;
     }
    return (addVMCode(FirstByte, vmCode, Length));
   }

  private boolean addVMCode(int firstByte, List<Byte> vmCode, int length)
   {
    BitInput Inp = new BitInput();
    Inp.InitBitInput();
    // memcpy(Inp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize));
    for (int i = 0; i < Math.min(BitInput.MAX_SIZE, vmCode.size()); i++)
     {
      Inp.getInBuf()[i] = vmCode.get(i);
     }
    rarVM.init();

    int FiltPos;
    if ((firstByte & 0x80) != 0)
     {
      FiltPos = RarVM.ReadData(Inp);
      if (FiltPos == 0)
       {
        initFilters();
       }
      else
       {
        FiltPos--;
       }
     }
    else
     FiltPos = lastFilter; // use the same filter as last time

    if (FiltPos > filters.size() || FiltPos > oldFilterLengths.size())
     {
      return (false);
     }
    lastFilter = FiltPos;
    boolean NewFilter = (FiltPos == filters.size());

    UnpackFilter StackFilter = new UnpackFilter(); // new filter for
    // PrgStack

    UnpackFilter Filter;
    if (NewFilter) // new filter code, never used before since VM reset
     {
      // too many different filters, corrupt archive
      if (FiltPos > 1024)
       {
        return (false);
       }

      // Filters[Filters.Size()-1]=Filter=new UnpackFilter;
      Filter = new UnpackFilter();
      filters.add(Filter);
      StackFilter.setParentFilter(filters.size() - 1);
      oldFilterLengths.add(0);
      Filter.setExecCount(0);
     }
    else
     // filter was used in the past
     {
      Filter = filters.get(FiltPos);
      StackFilter.setParentFilter(FiltPos);
      Filter.setExecCount(Filter.getExecCount() + 1);// ->ExecCount++;
     }

    prgStack.add(StackFilter);
    StackFilter.setExecCount(Filter.getExecCount());// ->ExecCount;

    int BlockStart = RarVM.ReadData(Inp);
    if ((firstByte & 0x40) != 0)
     {
      BlockStart += 258;
     }
    StackFilter.setBlockStart((BlockStart + unpPtr) & Compress.MAXWINMASK);
    if ((firstByte & 0x20) != 0)
     {
      StackFilter.setBlockLength(RarVM.ReadData(Inp));
     }
    else
     {
      StackFilter.setBlockLength(FiltPos < oldFilterLengths.size() ? oldFilterLengths.get(FiltPos) : 0);
     }
    StackFilter.setNextWindow((wrPtr != unpPtr) && ((wrPtr - unpPtr) & Compress.MAXWINMASK) <= BlockStart);

    // DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x
    // BlockStart=%08x",UnpPtr,WrPtr,BlockStart);

    oldFilterLengths.set(FiltPos, StackFilter.getBlockLength());

    // memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR));
    Arrays.fill(StackFilter.getPrg().getInitR(), 0);
    StackFilter.getPrg().getInitR()[3] = RarVM.VM_GLOBALMEMADDR;// StackFilter->Prg.InitR[3]=VM_GLOBALMEMADDR;
    StackFilter.getPrg().getInitR()[4] = StackFilter.getBlockLength();// StackFilter->Prg.InitR[4]=StackFilter->BlockLength;
    StackFilter.getPrg().getInitR()[5] = StackFilter.getExecCount();// StackFilter->Prg.InitR[5]=StackFilter->ExecCount;

    if ((firstByte & 0x10) != 0) // set registers to optional parameters
    // if any
     {
      int InitMask = Inp.fgetbits() >>> 9;
      Inp.faddbits(7);
      for (int I = 0; I < 7; I++)
       {
        if ((InitMask & (1 << I)) != 0)
         {
          // StackFilter->Prg.InitR[I]=RarVM::ReadData(Inp);
          StackFilter.getPrg().getInitR()[I] = RarVM.ReadData(Inp);
         }
       }
     }

    if (NewFilter)
     {
      int VMCodeSize = RarVM.ReadData(Inp);
      if (VMCodeSize >= 0x10000 || VMCodeSize == 0)
       {
        return (false);
       }
      byte[] VMCode = new byte[VMCodeSize];
      for (int I = 0; I < VMCodeSize; I++)
       {
        if (Inp.Overflow(3))
         {
          return (false);
         }
        VMCode[I] = (byte) (Inp.fgetbits() >> 8);
        Inp.faddbits(8);
       }
      // VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
      rarVM.prepare(VMCode, VMCodeSize, Filter.getPrg());
     }
    StackFilter.getPrg().setAltCmd(Filter.getPrg().getCmd());// StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0];
    StackFilter.getPrg().setCmdCount(Filter.getPrg().getCmdCount());// StackFilter->Prg.CmdCount=Filter->Prg.CmdCount;

    int StaticDataSize = Filter.getPrg().getStaticData().size();
    if (StaticDataSize > 0 && StaticDataSize < RarVM.VM_GLOBALMEMSIZE)
     {
      // read statically defined data contained in DB commands
      // StackFilter->Prg.StaticData.Add(StaticDataSize);
      StackFilter.getPrg().setStaticData(Filter.getPrg().getStaticData());
      // memcpy(&StackFilter->Prg.StaticData[0],&Filter->Prg.StaticData[0],StaticDataSize);
     }

    if (StackFilter.getPrg().getGlobalData().size() < RarVM.VM_FIXEDGLOBALSIZE)
     {
      // StackFilter->Prg.GlobalData.Reset();
      // StackFilter->Prg.GlobalData.Add(VM_FIXEDGLOBALSIZE);
      StackFilter.getPrg().getGlobalData().clear();
      StackFilter.getPrg().getGlobalData().setSize(RarVM.VM_FIXEDGLOBALSIZE);
     }

    // byte *GlobalData=&StackFilter->Prg.GlobalData[0];
    Vector<Byte> globalData = StackFilter.getPrg().getGlobalData();
    for (int I = 0; I < 7; I++)
     {
      rarVM.setLowEndianValue(globalData, I * 4, StackFilter.getPrg().getInitR()[I]);
     }

    // VM.SetLowEndianValue((uint
    // *)&GlobalData[0x1c],StackFilter->BlockLength);
    rarVM.setLowEndianValue(globalData, 0x1c, StackFilter.getBlockLength());
    // VM.SetLowEndianValue((uint *)&GlobalData[0x20],0);
    rarVM.setLowEndianValue(globalData, 0x20, 0);
    rarVM.setLowEndianValue(globalData, 0x24, 0);
    rarVM.setLowEndianValue(globalData, 0x28, 0);

    // VM.SetLowEndianValue((uint
    // *)&GlobalData[0x2c],StackFilter->ExecCount);
    rarVM.setLowEndianValue(globalData, 0x2c, StackFilter.getExecCount());
    // memset(&GlobalData[0x30],0,16);
    for (int i = 0; i < 16; i++)
     {
      globalData.set(0x30 + i, Byte.valueOf((byte) (0)));
     }
    if ((firstByte & 8) != 0) // put data block passed as parameter if any
     {
      if (Inp.Overflow(3))
       {
        return (false);
       }
      int DataSize = RarVM.ReadData(Inp);
      if (DataSize > RarVM.VM_GLOBALMEMSIZE - RarVM.VM_FIXEDGLOBALSIZE)
       {
        return (false);
       }
      int CurSize = StackFilter.getPrg().getGlobalData().size();
      if (CurSize < DataSize + RarVM.VM_FIXEDGLOBALSIZE)
       {
        // StackFilter->Prg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize);
        StackFilter.getPrg().getGlobalData().setSize(DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize);
       }
      int offset = RarVM.VM_FIXEDGLOBALSIZE;
      globalData = StackFilter.getPrg().getGlobalData();
      for (int I = 0; I < DataSize; I++)
       {
        if (Inp.Overflow(3))
         {
          return (false);
         }
        globalData.set(offset + I, Byte.valueOf((byte) (Inp.fgetbits() >>> 8)));
        Inp.faddbits(8);
       }
     }
    return (true);
   }

  private void ExecuteCode(VMPreparedProgram Prg)
   {
    if (Prg.getGlobalData().size() > 0)
     {
      // Prg->InitR[6]=int64to32(WrittenFileSize);
      Prg.getInitR()[6] = (int) (writtenFileSize);
      // rarVM.SetLowEndianValue((uint
      // *)&Prg->GlobalData[0x24],int64to32(WrittenFileSize));
      rarVM.setLowEndianValue(Prg.getGlobalData(), 0x24, (int) writtenFileSize);
      // rarVM.SetLowEndianValue((uint
      // *)&Prg->GlobalData[0x28],int64to32(WrittenFileSize>>32));
      rarVM.setLowEndianValue(Prg.getGlobalData(), 0x28, (int) (writtenFileSize >>> 32));
      rarVM.execute(Prg);
     }
   }

  // Duplicate method
  // private boolean ReadEndOfBlock() throws IOException, RarException
  // {
  // int BitField = getbits();
  // boolean NewTable, NewFile = false;
  // if ((BitField & 0x8000) != 0) {
  // NewTable = true;
  // addbits(1);
  // } else {
  // NewFile = true;
  // NewTable = (BitField & 0x4000) != 0;
  // addbits(2);
  // }
  // tablesRead = !NewTable;
  // return !(NewFile || NewTable && !readTables());
  // }

  public boolean isFileExtracted()
   {
    return fileExtracted;
   }

  public void setDestSize(long destSize)
   {
    this.destUnpSize = destSize;
    this.fileExtracted = false;
   }

  public void setSuspended(boolean suspended)
   {
    this.suspended = suspended;
   }

  public int getChar() throws IOException, RarException
   {
    if (inAddr > BitInput.MAX_SIZE - 30)
     {
      unpReadBuf();
     }
    return (inBuf[inAddr++] & 0xff);
   }

  public int getPpmEscChar()
   {
    return ppmEscChar;
   }

  public void setPpmEscChar(int ppmEscChar)
   {
    this.ppmEscChar = ppmEscChar;
   }

  public void cleanUp()
   {
    if (ppm != null)
     {
      SubAllocator allocator = ppm.getSubAlloc();
      if (allocator != null)
       {
        allocator.stopSubAllocator();
       }
     }
   }
 }
